<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <!-- 用来执行矩阵操作的库 -->
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"
      integrity="sha512-zhHQR0/H5SEBL3Wn6yYSaTTZej12z0hVZKOv3TwCUXT1z5qeqGcXJLLrbERYRScEDDpYIJhPC1fk31gqR783iQ=="
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <canvas id="glcanvas" width="640" height="480"></canvas>
  </body>

  <script>
    // https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API/Tutorial

    const canvas = document.querySelector('canvas');
    /** @type {WebGLRenderingContext} */
    const gl = canvas.getContext('webgl');
    /** 当视频可以复制到纹理中时将被设置为 true */
    let copyVideo = false;

    // 旋转相关
    let cubeRotation = 0;
    let deltaTime = 0;

    // 顶点着色器
    const vsSource = `
    // 数据类型为4维向量
    attribute vec4 aVertexPosition;
    // attribute vec4 aVertexColor;
    attribute vec2 aTextureCoord;
    attribute vec3 aVertexNormal;
 

    // 模型视图矩阵，用于将顶点从模型空间转换到摄像机空间。
    uniform mat4 uModelViewMatrix;
    // 投影矩阵，用于将摄像机空间中的坐标转换到裁剪空间。
    uniform mat4 uProjectionMatrix;
    uniform mat4 uNormalMatrix;

    // varying lowp vec4 vColor;
    varying highp vec2 vTextureCoord;
    varying highp vec3 vLighting;

    void main() {
      // 顶点的位置通过将其乘以模型视图矩阵和投影矩阵来计算，最终结果赋值给内置变量 
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;

      // vColor = aVertexColor;
      vTextureCoord = aTextureCoord;

      // Apply lighting effect
      highp vec3 ambientLight = vec3(0.3, 0.3, 0.3);
      highp vec3 directionalLightColor = vec3(1, 1, 1);
      highp vec3 directionalVector = normalize(vec3(0.85, 0.8, 0.75));

      highp vec4 transformedNormal = uNormalMatrix * vec4(aVertexNormal, 1.0);

      highp float directional = max(dot(transformedNormal.xyz, directionalVector), 0.0);
      vLighting = ambientLight + (directionalLightColor * directional);
    }
  `;
    // 片段着色器
    const fsSource = `
    // varying lowp vec4 vColor;
    varying highp vec2 vTextureCoord;
    varying highp vec3 vLighting;

    uniform sampler2D uSampler;

    void main() {
      // 设置颜色为白色
      // gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

      // 设置渐变色
      // gl_FragColor = vColor;

      // 纹理贴图
      highp vec4 texelColor = texture2D(uSampler, vTextureCoord);

      // 灯光
      gl_FragColor = vec4(texelColor.rgb * vLighting, texelColor.a);
    }
  `;

    /**
     * 初始化着色器程序，让 WebGL 知道如何绘制我们的数据
     * @param {WebGLRenderingContext} gl WebGL 上下文
     * @param {String} vsSource 顶点着色器源码
     * @param {String} fsSource 片段着色器源码
     * @return {Object} 着色器程序
     */
    function initShaderProgram(gl, vsSource, fsSource) {
      const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
      const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

      // 创建着色器程序
      const shaderProgram = gl.createProgram();
      gl.attachShader(shaderProgram, vertexShader);
      gl.attachShader(shaderProgram, fragmentShader);
      gl.linkProgram(shaderProgram);

      // 如果创建失败，alert
      if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert(
          'Unable to initialize the shader program: ' +
            gl.getProgramInfoLog(shaderProgram)
        );
        return null;
      }

      return shaderProgram;
    }

    // 创建指定类型的着色器，上传 source 源码并编译
    function loadShader(gl, type, source) {
      const shader = gl.createShader(type);

      // 发送源到着色器对象
      gl.shaderSource(shader, source);

      // 编译着色器程序
      gl.compileShader(shader);

      // See if it compiled successfully
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(
          'An error occurred compiling the shaders: ' +
            gl.getShaderInfoLog(shader)
        );
        gl.deleteShader(shader);
        return null;
      }

      return shader;
    }

    // 创建缓冲器，用来存储顶点
    function initBuffers(gl) {
      const positionBuffer = initPositionBuffer(gl);
      const colorBuffer = initColorBuffer(gl);
      const indexBuffer = initIndexBuffer(gl);
      const textureCoordBuffer = initTextureBuffer(gl);
      const normalBuffer = initNormalBuffer(gl);

      return {
        position: positionBuffer,
        // color: colorBuffer,
        textureCoord: textureCoordBuffer,
        indices: indexBuffer,
        normal: normalBuffer,
      };
    }
    function initPositionBuffer(gl) {
      // 为正方形的位置创建一个缓冲区
      const positionBuffer = gl.createBuffer();

      // 从这里开始，选择positionBuffer作为应用缓冲区操作的对象
      gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

      //现在为正方形创建一个位置数组
      const positions = [
        // Front face
        -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0,

        // Back face
        -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,

        // Top face
        -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,

        // Right face
        1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0,

        // Left face
        -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,
      ];

      // 现在将位置列表传递给WebGL来构建形状。为此，我们从JavaScript数组中
      // 创建一个Float32Array，然后用它来填充当前缓冲区。
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(positions),
        gl.STATIC_DRAW
      );

      return positionBuffer;
    }
    /**
     * 将顶点颜色放到缓冲区
     * @param {WebGLRenderingContext} gl
     */
    function initColorBuffer(gl) {
      const faceColors = [
        [1.0, 1.0, 1.0, 1.0], // Front face: white
        [1.0, 0.0, 0.0, 1.0], // Back face: red
        [0.0, 1.0, 0.0, 1.0], // Top face: green
        [0.0, 0.0, 1.0, 1.0], // Bottom face: blue
        [1.0, 1.0, 0.0, 1.0], // Right face: yellow
        [1.0, 0.0, 1.0, 1.0], // Left face: purple
      ];

      // Convert the array of colors into a table for all the vertices.
      let colors = [];
      for (let j = 0; j < faceColors.length; j++) {
        const c = faceColors[j];
        colors = colors.concat(c, c, c, c);
      }

      const colorBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

      return colorBuffer;
    }
    /**
     * 定义元素（三角形）数组
     * @param {WebGLRenderingContext} gl
     */
    function initIndexBuffer(gl) {
      const indexBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

      // 这个数组将每个面定义为两个三角形，使用索引到顶点数组指定每个三角形的位置。
      const indices = [
        0,
        1,
        2,
        0,
        2,
        3, // front
        4,
        5,
        6,
        4,
        6,
        7, // back
        8,
        9,
        10,
        8,
        10,
        11, // top
        12,
        13,
        14,
        12,
        14,
        15, // bottom
        16,
        17,
        18,
        16,
        18,
        19, // right
        20,
        21,
        22,
        20,
        22,
        23, // left
      ];

      // Now send the element array to GL
      gl.bufferData(
        gl.ELEMENT_ARRAY_BUFFER,
        new Uint16Array(indices),
        gl.STATIC_DRAW
      );

      return indexBuffer;
    }
    /**
     * 将纹理数据放到缓冲区
     * @param {WebGLRenderingContext} gl
     */
    function initTextureBuffer(gl) {
      const textureCoordBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);

      // 纹理坐标
      const textureCoordinates = [
        // Front
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        // Back
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        // Top
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        // Bottom
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        // Right
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        // Left
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
      ];

      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(textureCoordinates),
        gl.STATIC_DRAW
      );

      return textureCoordBuffer;
    }
    /**
     * 将顶点的法线数据放到缓冲区
     * @param {WebGLRenderingContext} gl
     */
    function initNormalBuffer(gl) {
      const normalBuffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);

      var vertexNormals = [
        // Front
        0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,

        // Back
        0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,

        // Top
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,

        // Bottom
        0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0,

        // Right
        1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,

        // Left
        -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
      ];

      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(vertexNormals),
        gl.STATIC_DRAW
      );

      return normalBuffer;
    }

    /**
     * 绘制场景
     * @param {WebGLRenderingContext} gl
     */
    function drawScene(gl, programInfo, buffers, texture, cubeRotation) {
      gl.clearColor(0.0, 0.0, 0.0, 1.0); // 用黑色擦除画布
      gl.clearDepth(1.0); // 清除所有内容
      gl.enable(gl.DEPTH_TEST); // 启动深度检测
      gl.depthFunc(gl.LEQUAL); // 近处的事物使远处的事物模糊不清
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 清除了颜色缓冲区和深度缓冲区

      // 创建一个透视矩阵，一个特殊的矩阵，用来模拟相机的透视失真。
      // 我们的视野是45度，宽度/高度比例与画布的显示尺寸相匹配，我们
      // 只希望看到距离相机0.1到100个单位之间的物体。
      const fieldOfView = (45 * Math.PI) / 180; // in radians
      const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      const zNear = 0.1;
      const zFar = 100.0;
      const projectionMatrix = mat4.create();

      // 注意：glmatrix.js 总是将第一个参数作为接收结果的目的地。
      mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);

      // 将绘图位置设置为“身份”点，即场景的中心。
      const modelViewMatrix = mat4.create();

      // 现在将绘制位置移动到我们想要开始绘制正方形的位置。

      mat4.translate(
        modelViewMatrix, // 目标矩阵
        modelViewMatrix, // 转移矩阵
        [-0.0, 0.0, -6.0]
      ); // amount to translate

      mat4.rotate(
        modelViewMatrix, // destination matrix
        modelViewMatrix, // matrix to rotate
        cubeRotation, // amount to rotate in radians
        [0, 0, 1]
      ); // axis to rotate around (Z)
      mat4.rotate(
        modelViewMatrix, // destination matrix
        modelViewMatrix, // matrix to rotate
        cubeRotation * 0.7, // amount to rotate in radians
        [0, 1, 0]
      ); // axis to rotate around (Y)
      mat4.rotate(
        modelViewMatrix, // destination matrix
        modelViewMatrix, // matrix to rotate
        cubeRotation * 0.3, // amount to rotate in radians
        [1, 0, 0]
      ); // axis to rotate around (X)

      // 实现灯光相关的代码
      const normalMatrix = mat4.create();
      mat4.invert(normalMatrix, modelViewMatrix);
      mat4.transpose(normalMatrix, normalMatrix);

      setPositionAttribute(gl, buffers, programInfo);
      // setColorAttribute(gl, buffers, programInfo);
      setTextureAttribute(gl, buffers, programInfo);

      // 告诉WebGL使用哪些索引来索引顶点
      gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);

      // 法线
      setNormalAttribute(gl, buffers, programInfo);

      // Tell WebGL to use our program when drawing
      gl.useProgram(programInfo.program);

      // Set the shader uniforms
      gl.uniformMatrix4fv(
        programInfo.uniformLocations.projectionMatrix,
        false,
        projectionMatrix
      );
      gl.uniformMatrix4fv(
        programInfo.uniformLocations.modelViewMatrix,
        false,
        modelViewMatrix
      );
      gl.uniformMatrix4fv(
        programInfo.uniformLocations.normalMatrix,
        false,
        normalMatrix
      );

      // 告诉WebGL我们想要影响纹理单位0
      gl.activeTexture(gl.TEXTURE0);
      //将纹理绑定到纹理单元0
      gl.bindTexture(gl.TEXTURE_2D, texture);
      // 告诉着色器我们将纹理绑定到纹理单元0
      gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

      // 画出对象
      {
        const vertexCount = 36;
        const type = gl.UNSIGNED_SHORT;
        const offset = 0;
        gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
      }
    }
    // Tell WebGL how to pull out the colors from the color buffer
    // into the vertexColor attribute.
    function setColorAttribute(gl, buffers, programInfo) {
      const numComponents = 4;
      const type = gl.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
      gl.vertexAttribPointer(
        programInfo.attribLocations.vertexColor,
        numComponents,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
    }
    // 告诉WebGL如何从位置缓冲区中提取位置到 vertexPosition 属性中。
    // buffer into the vertexPosition attribute.
    function setPositionAttribute(gl, buffers, programInfo) {
      const numComponents = 3; // 每次迭代取出的值
      const type = gl.FLOAT; // 缓冲区中的数据为32位浮点数
      const normalize = false; // don't normalize
      const stride = 0; // 从一组值转换到下一组值需要多少字节
      // 0 = use type and numComponents above
      const offset = 0; // 从缓冲区内多少字节开始
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
      gl.vertexAttribPointer(
        programInfo.attribLocations.vertexPosition,
        numComponents,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
    }
    function setTextureAttribute(gl, buffers, programInfo) {
      const num = 2; // 每个坐标由 2 个值组成
      const type = gl.FLOAT; // 缓冲区中的数据为 32 位浮点数
      const normalize = false; // 不做标准化处理
      const stride = 0; // 从一个坐标到下一个坐标要获取多少字节
      const offset = 0; // 从缓冲区内的第几个字节开始获取数据
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
      gl.vertexAttribPointer(
        programInfo.attribLocations.textureCoord,
        num,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
    }
    function setNormalAttribute(gl, buffers, programInfo) {
      const numComponents = 3;
      const type = gl.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normal);
      gl.vertexAttribPointer(
        programInfo.attribLocations.vertexNormal,
        numComponents,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.vertexNormal);
    }

    /** 初始化着色器程序 */
    const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
    /** 存储起来，便于使用 */
    const programInfo = {
      program: shaderProgram,
      attribLocations: {
        vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
        // vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
        textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'),
        vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'),
      },
      uniformLocations: {
        projectionMatrix: gl.getUniformLocation(
          shaderProgram,
          'uProjectionMatrix'
        ),
        modelViewMatrix: gl.getUniformLocation(
          shaderProgram,
          'uModelViewMatrix'
        ),
        uSampler: gl.getUniformLocation(shaderProgram, 'uSampler'),
        normalMatrix: gl.getUniformLocation(shaderProgram, 'uNormalMatrix'),
      },
    };

    /**
     * 加载贴图
     * @param {WebGLRenderingContext} gl
     */
    function loadTexture(gl, url) {
      const texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);

      // 由于图像必须通过互联网下载，因此可能需要一些时间才能准备好。
      // 在此之前，在纹理中放置一个像素，以便我们可以立即使用它。当图像下载完成后，我们将使用图像的内容更新纹理。
      const level = 0;
      const internalFormat = gl.RGBA;
      const width = 1;
      const height = 1;
      const border = 0;
      const srcFormat = gl.RGBA;
      const srcType = gl.UNSIGNED_BYTE;
      const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
      gl.texImage2D(
        gl.TEXTURE_2D,
        level,
        internalFormat,
        width,
        height,
        border,
        srcFormat,
        srcType,
        pixel
      );

      const image = new Image();
      image.onload = () => {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(
          gl.TEXTURE_2D,
          level,
          internalFormat,
          srcFormat,
          srcType,
          image
        );

        // WebGL1对2次图像和非2次图像有不同的要求，所以检查图像在两个维度上是否都是2的幂。
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
          gl.generateMipmap(gl.TEXTURE_2D);
          return;
        }

        // No, it's not a power of 2. Turn off mips and set
        // wrapping to clamp to edge
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      };
      image.src = url;

      return texture;
    }
    function isPowerOf2(value) {
      return (value & (value - 1)) === 0;
    }

    /** 创建一个空的纹理对象，为视频帧作为纹理而准备 */
    function initTexture(gl) {
      const texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);

      // 因为视频必须通过互联网下载
      // 可能需要一些时间才能准备好
      // 因此在纹理中放置一个像素，以便我们
      // 可以立即使用它。
      const level = 0;
      const internalFormat = gl.RGBA;
      const width = 1;
      const height = 1;
      const border = 0;
      const srcFormat = gl.RGBA;
      const srcType = gl.UNSIGNED_BYTE;
      const pixel = new Uint8Array([0, 0, 255, 255]); // 不透明的蓝色
      gl.texImage2D(
        gl.TEXTURE_2D,
        level,
        internalFormat,
        width,
        height,
        border,
        srcFormat,
        srcType,
        pixel
      );

      // 关闭 mips 并将包裹（wrapping）设置为边缘分割（clamp to edge）
      // 这样无论视频的尺寸如何，都可以正常工作。
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

      return texture;
    }
    /** 更新纹理对象 */
    function updateTexture(gl, texture, video) {
      const level = 0;
      const internalFormat = gl.RGBA;
      const srcFormat = gl.RGBA;
      const srcType = gl.UNSIGNED_BYTE;
      gl.bindTexture(gl.TEXTURE_2D, texture);
      gl.texImage2D(
        gl.TEXTURE_2D,
        level,
        internalFormat,
        srcFormat,
        srcType,
        video
      );
    }
    /** 创建一个视频 */
    function setupVideo(url) {
      const video = document.createElement('video');

      let playing = false;
      let timeupdate = false;

      video.playsInline = true;
      video.muted = true;
      video.loop = true;

      // 等待以下两个事件
      // 确保 video 中已有数据
      video.addEventListener(
        'playing',
        () => {
          playing = true;
          checkReady();
        },
        true
      );

      video.addEventListener(
        'timeupdate',
        () => {
          timeupdate = true;
          checkReady();
        },
        true
      );

      video.src = url;
      video.play();

      function checkReady() {
        if (playing && timeupdate) {
          copyVideo = true;
        }
      }

      return video;
    }

    // 这里是我们调用构建我们将要绘制的所有对象的例行程序的地方
    const buffers = initBuffers(gl);

    // 加载纹理
    // const texture = loadTexture(gl, 'cubetexture.png');
    // 将图像像素按WebGL期望的从下到上的顺序翻转。
    // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    const texture = initTexture(gl);
    const video = setupVideo('Firefox.mp4');

    let then = 0;
    // 每帧上调用绘制函数
    function render(now) {
      now *= 0.001; // 转换成秒的单位
      deltaTime = now - then;
      then = now;

      // 当视频数据加载成功时，更新纹理
      if (copyVideo) {
        updateTexture(gl, texture, video);
      }

      drawScene(gl, programInfo, buffers, texture, cubeRotation);
      cubeRotation += deltaTime;

      requestAnimationFrame(render);
    }
    requestAnimationFrame(render);
  </script>
</html>
